//#define _CACHE
#define _GENERAL_P

using System;
using System.Collections.Generic;
using System.ComponentModel;

using EnvDTE;

namespace CodepageMaintain
{
	[TypeDescriptionProvider(typeof(DtePropertiesObjectTDP))]
	[TypeConverter(typeof(ExpandableObjectConverter))]
	internal class DtePropertiesObject
	{
		private readonly Properties _properties;

		private readonly object _item;

		public DtePropertiesObject(Properties properties, object item)
		{
			this._properties = properties;
			this._item = item;
		}

		public object Item
		{
			get { return this._item; }
		}

		public Properties Properties
		{
			get { return this._properties; }
		}
	}

	internal class DtePropertiesObjectTDP : TypeDescriptionProvider
	{
		private static readonly TypeDescriptionProvider _tdp =
			TypeDescriptor.GetProvider(typeof(DtePropertiesObject));

		public DtePropertiesObjectTDP()
			: base(_tdp)
		{
		}

		public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
		{
			var baseTD = base.GetTypeDescriptor(objectType, instance);
			return instance == null ? baseTD : new DtePropertiesObjectTD(baseTD, instance);
		}
	}

	internal class DtePropertiesObjectTD : CustomTypeDescriptor
	{
		private readonly DtePropertiesObject _holder;

		private readonly PropertyDescriptorCollection _properties;

		public DtePropertiesObjectTD(ICustomTypeDescriptor parent, object instance)
			: base(parent)
		{
			if (instance != null && instance is DtePropertiesObject)
			{
				_holder = (DtePropertiesObject)instance;
			}
			else
			{
				throw new Exception("unknown instance provided");
			}

#if _GENERAL_P
			PropertyDescriptorCollection generalProps;

			if (_holder.Item is Solution)
			{
				generalProps = TypeDescriptor.GetProperties(_holder.Item as Solution);
			}
			else if (_holder.Item is Project)
			{
				generalProps = TypeDescriptor.GetProperties(_holder.Item as Project);
			}
			else if (_holder.Item is ProjectItem)
			{
				generalProps = TypeDescriptor.GetProperties(_holder.Item as ProjectItem);
			}
			else
			{
				generalProps = TypeDescriptor.GetProperties(_holder.Item);
			}
#endif

			var props = new List<PropertyDescriptor>(_holder.Properties.Count
#if _GENERAL_P
				+ generalProps.Count
#endif
				);
			foreach (Property property in _holder.Properties)
			{
				props.Add(new DtePropertyDescriptor(property, "Properties"
#if _CACHE
					+ " (cached)"
#endif
					));
			}

#if _GENERAL_P
			if (_holder.Item != null)
			{
				for (var i = 0; i < generalProps.Count; i++)
				{
					var p = generalProps[i];
					var attrs = new List<Attribute>(p.Attributes.Count + 1);
					foreach (Attribute attr in p.Attributes)
					{
						attrs.Add(attr);
					}
					var bro = attrs.Find(obj => obj is BrowsableAttribute);
					if (bro != null)
					{
						attrs.Remove(bro);
					}
					attrs.Add(new BrowsableAttribute(true));
					attrs.Add(new CategoryAttribute("General"
#if _CACHE
						+ " (cached)"
#endif
						));

					props.Add(new PropertyDescriptor2(p, attrs.ToArray(), _holder.Item));
				}
			}
#endif

			_properties = new PropertyDescriptorCollection(props.ToArray(), true);
		}

		public override PropertyDescriptorCollection GetProperties()
		{
			return GetProperties(new Attribute[0]);
		}

		public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
		{
			//ignore attribs ;)
			return _properties;
		}
	}

#if _GENERAL_P
	internal class PropertyDescriptor2 : PropertyDescriptor
	{
		private readonly PropertyDescriptor _back;

		private readonly object _instance;

#if _CACHE
		private object _cachedValue;

		private bool _isCached;
#endif

		public PropertyDescriptor2(PropertyDescriptor back, Attribute[] attrs, object instance)
			: base(back.Name, attrs)
		{
			this._back = back;
			this._instance = instance;
		}

		public override bool CanResetValue(object component)
		{
			return _back.CanResetValue(_instance);
		}

		public override Type ComponentType
		{
			get { return _back.ComponentType; }
		}

		public override object GetValue(object component)
		{
#if _CACHE
			if (!_isCached)
			{
				_cachedValue = _back.GetValue(_instance);
				_isCached = true;
			}
			return _cachedValue;
#else
			return _back.GetValue(_instance);
#endif
		}

		public override bool IsReadOnly
		{
			get { return _back.IsReadOnly; }
		}

		public override Type PropertyType
		{
			get
			{
#if _CACHE
				if (_isCached && _cachedValue != null)
				{
					return _cachedValue.GetType();
				}
#endif
				return _back.PropertyType;
			}
		}

		public override void ResetValue(object component)
		{
			_back.ResetValue(_instance);
		}

		public override void SetValue(object component, object value)
		{
			_back.SetValue(_instance, value);
		}

		public override bool ShouldSerializeValue(object component)
		{
			return _back.ShouldSerializeValue(_instance);
		}
	}
#endif

	internal class DtePropertyDescriptor : PropertyDescriptor
	{
		private readonly Property _property;

#if _CACHE
		private object _cachedValue;

		private bool _isCached;
#endif

		public DtePropertyDescriptor(Property property, string category)
			: base(
				property.Name, new Attribute[] {new BrowsableAttribute(true), new CategoryAttribute(category)})
		{
			_property = property;
		}

		public override bool CanResetValue(object component)
		{
			return false;
		}

		public override Type ComponentType
		{
			get { return typeof(DtePropertiesObject); }
		}

		public override object GetValue(object component)
		{
#if _CACHE
			if (!_isCached)
			{
#endif
				try
				{
#if _CACHE
					_cachedValue = _property.Value;
#else
				return _property.Value;
#endif
				}
				catch (Exception e)
				{
#if _CACHE
					_cachedValue = e.Message;
#else
				return e.Message;
#endif
				}
#if _CACHE
				finally
				{
					_isCached = true;
				}
			}
			return _cachedValue;
#endif
		}

		public override bool IsReadOnly
		{
			get { return true; }
		}

		public override Type PropertyType
		{
			get
			{
				try
				{
#if _CACHE
					if (_isCached)
					{
						return _cachedValue == null ? typeof(object) : _cachedValue.GetType();
					}
#endif
					return _property.Value.GetType();
				}
				catch (Exception)
				{
					return typeof(object);
				}
			}
		}

		public override void ResetValue(object component)
		{
			throw new NotSupportedException("Could not reset");
		}

		public override void SetValue(object component, object value)
		{
			throw new NotSupportedException("Could not set");
		}

		public override bool ShouldSerializeValue(object component)
		{
			return false;
		}
	}
}